<script setup>
import { computed, ref, watch, onUnmounted, nextTick } from "vue";
import { calcTooltipPosition } from "../calcTooltipPosition";
import { useMouse } from "../useMouse";
import { setOpacity } from "../lib";

const props = defineProps({
    backgroundColor: {
        type: String,
        default: "#FFFFFF"
    },
    color: {
        type: String,
        default: "#000000"
    },
    content: String,
    maxWidth: {
        type: String,
        default: '300px'
    },
    parent: {
        type: Object
    },
    show: {
        type: Boolean,
        default: false,
    },
    isCustom: {
        type: Boolean,
        default: false,
    },
    fontSize: {
        type: [Number, String],
        default: 14
    },
    borderRadius: {
        type: Number,
        default: 4
    },
    borderColor: {
        type: String,
        default: '#e1e5e8'
    },
    borderWidth: {
        type: Number,
        default: 1
    },
    backgroundOpacity: {
        type: Number,
        default: 100,
    },
    position: {
        type: String,
        default: "center"
    },
    offsetY: {
        type: Number,
        default: 24
    },
    blockShiftY: {
        type: Boolean,
        default: false,
    },
    isFullscreen: {
        type: Boolean,
        default: false
    },
    smooth: {
        type: Boolean,
        default: true
    },
    backdropFilter: {
        type: Boolean,
        default: true
    },
    smoothForce: {
        type: Number,
        default: 0.18
    },
    smoothSnapThreshold: {
        type: Number,
        default: 0.25
    }
});

const tooltip = ref(null);

const { x, y } = useMouse(props.parent);
const targetPosition = ref({ x: 0, y: 0 });
const displayPosition = ref({ x: 0, y: 0 });

function placeTooltip({ x: _x, y: _y }) {
    x.value = _x;
    y.value = _y;
}

let animationFrameId = null;

function stepAnimation() {
    if (!props.show) {
        cancelAnimation();
        return;
    }

    if (!props.smooth) {
        displayPosition.value.x = targetPosition.value.x;
        displayPosition.value.y = targetPosition.value.y;
        cancelAnimation();
        return;
    }

    const dx = targetPosition.value.x - displayPosition.value.x;
    const dy = targetPosition.value.y - displayPosition.value.y;

    if (Math.abs(dx) <= props.smoothSnapThreshold && Math.abs(dy) <= props.smoothSnapThreshold) {
        displayPosition.value.x = targetPosition.value.x;
        displayPosition.value.y = targetPosition.value.y;
        cancelAnimation();
        return;
    }

    displayPosition.value.x += dx * props.smoothForce;
    displayPosition.value.y += dy * props.smoothForce;

    animationFrameId = requestAnimationFrame(stepAnimation);
}

function ensureAnimationRunning() {
    if (animationFrameId == null && props.show && props.smooth) {
        animationFrameId = requestAnimationFrame(stepAnimation);
    }
}

function cancelAnimation() {
    if (animationFrameId != null) {
        cancelAnimationFrame(animationFrameId);
        animationFrameId = null;
    }
}

watch([x, y], ([newX, newY]) => {
    targetPosition.value.x = newX;
    targetPosition.value.y = newY;

    if (!props.smooth) {
        displayPosition.value.x = newX;
        displayPosition.value.y = newY;
    } else {
        ensureAnimationRunning();
    }
});

watch(
    () => props.show,
    async (show) => {
        if (show) {
            const initialX = x.value;
            const initialY = y.value;
            targetPosition.value.x = initialX;
            targetPosition.value.y = initialY;
            displayPosition.value.x = initialX;
            displayPosition.value.y = initialY;

            await nextTick();
            ensureAnimationRunning();
        } else {
            cancelAnimation();
        }
    }
);

onUnmounted(() => {
    cancelAnimation();
});

const pixelPosition = computed(() => {
    const pos = calcTooltipPosition({
        tooltip: tooltip.value,
        chart: props.parent,
        clientPosition: displayPosition.value,
        positionPreference: props.position,
        defaultOffsetY: props.offsetY,
        blockShiftY: props.blockShiftY
    });
    return {
        top: Math.round(pos.top),
        left: Math.round(pos.left)
    };
});

const convertedBackground = computed(() => {
    return setOpacity(props.backgroundColor, props.backgroundOpacity);
});

const tooltipStyle = computed(() => {
    const base = {
        pointerEvents: "none",
        position: "fixed",
        top: "0px",
        left: "0px",
        transform: `translate3d(${pixelPosition.value.left}px, ${pixelPosition.value.top}px, 0)`,
        borderRadius: `${props.borderRadius}px`,
        border: `${props.borderWidth}px solid ${props.borderColor}`,
        zIndex: 2147483647
    };

    if (!props.isCustom) {
        Object.assign(base, {
            background: convertedBackground.value,
            color: props.color,
            maxWidth: props.maxWidth,
            fontSize: `${props.fontSize}px`
        });
    }

    return base;
});

defineExpose({
    placeTooltip
});

</script>

<template>
    <teleport :to="isFullscreen ? parent : 'body'">
        <div 
            ref="tooltip" 
            role="tooltip" 
            :aria-hidden="!show" 
            aria-live="polite" 
            data-cy="tooltip" 
            v-if="show" 
            :class="{
                'vue-data-ui-custom-tooltip': isCustom,
                'vue-data-ui-tooltip': !isCustom,
                'vue-data-ui-tooltip-backdrop': backdropFilter
            }" 
            :style="tooltipStyle"
        >
            <slot name="tooltip-before" />
            <slot />
            <div v-html="content" />
            <slot name="tooltip-after" />
        </div>
    </teleport>
</template>

<style>
.vue-data-ui-tooltip {
    box-shadow: 0 6px 12px -6px rgba(0, 0, 0, 0.2);
    position: fixed;
    padding: 12px;
}

.vue-data-ui-tooltip-backdrop {
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}

.vue-data-ui-custom-tooltip {
    position: fixed;
    z-index: 3;
}

.vue-data-ui-tooltip,
.vue-data-ui-custom-tooltip {
    will-change: transform;
}
</style>
